package stmlib.examples;

import java.util.*;
import stmlib.*;

/**
 * STMlib-based Data Access Layer for an IRC-like chat service.
 * @author Seun Osewa
 */
public class IRC {
    public static void main(String[] args) {
        //using the data access layer:
        long sessionId1 = Tx.login("seun");
        long sessionId2 = Tx.login("user2");
        Tx.createRoom("c#");
        Tx.createRoom("java");
        Tx.joinRoom(sessionId1, "c#");
        Tx.joinRoom(sessionId2, "java");
        Tx.joinRoom(sessionId1, "java");

        new TransactionT.V() { public void atomicV() {
            Stm.println(users); Stm.println("");
            Stm.println(rooms); Stm.println("");
            Stm.println(sessions); Stm.println("");
        }}.execute();
    }
    public static class UserData {
        final StmSet<String> watching = new StmSet(); //transactional data type.
        final Var<Long> sessionId = new Var(0L);  //value type (immutable) wrapped in a Var.
        final Var<Long> lastActive = new Var(System.currentTimeMillis());
        final Var<Integer> lastChat = new Var(0);
        public String toString() { return ("UserData(" + watching + ", "+ sessionId  + ", "
                + lastActive + ", " + lastChat + ")"); }
    }
    public static StmMap<String, UserData> users = new StmMap<String, UserData>();

    public static class RoomData {
        final StmSet<String> watchers = new StmSet(); //number of users in this room
        public String toString() { return ("RoomData(" + watchers + ", " + /*chatLog +*/ ")"); }
    }
    public static StmMap<String, RoomData> rooms = new StmMap<String, RoomData>();

    public static class SessionData {
        final String user; //read-only fields don't have to be wrapped in Vars.
        public SessionData(String user) { this.user = user; }
    }
    public static StmMap<Long, SessionData> sessions = new StmMap<Long, SessionData>();

    public static long login(String name) {
        if (users.containsKey(name)) throw new DataException("already registered");
        else {
            UserData ud = new UserData();
            users.put(name, ud);

            final long sessionId = new Random().nextLong();
            ud.sessionId.set(sessionId);
            sessions.put(sessionId, new SessionData(name));
            return sessionId;
        }        
    }

    public static void logout(long sessionId) {
        String userName = getSessionData(sessionId).user;
        UserData ud = getUserData(userName);
        for (String roomName: ud.watching) {
            getRoomData(roomName).watchers.remove(userName);
        }
        sessions.remove(ud.sessionId.get());
        users.remove(userName);
    }

    public static void createRoom (String roomName) {
        if (!rooms.containsKey(roomName)) rooms.put(roomName, new RoomData());
    }

    public static void joinRoom(long sessionId, String roomName) {
        String userName = getSessionData(sessionId).user;
        RoomData rd = getRoomData(roomName);
        rd.watchers.add(userName);
        users.get(userName).watching.add(roomName);
    }
    
    public static boolean leaveRoom(long sessionId, String roomName) {
        String userName = getSessionData(sessionId).user;
        RoomData rd = getRoomData(roomName);
        final boolean modified = rd.watchers.remove(userName);
        users.get(userName).watching.remove(roomName);
        return modified;
    }

    public static <TK, TD> TD getData(TK key, StmMap<TK, TD> source, String msg) {
        final TD td = source.get(key);
        if (td == null) throwQ(msg);
        return td;
    }

    public static RoomData getRoomData(String roomName) {
        return getData(roomName, rooms, "room not found");
    }

    public static SessionData getSessionData(long sessionId) {
        return getData(sessionId, sessions, "invalid session");
    }

    public static UserData getUserData(String userName) {
        return getData(userName, users, "User doesn't exist");
    }

    public static class DataException extends RuntimeException {
        public DataException(String message) { super(message); }
    }
    public static void throwQ(String message) { throw new DataException(message); }


    /** The functions in this class wrap each data access routine above in a transaction*/
    public static class Tx {
        public static Long login(final String name) {
            return (new TransactionT<Long>(){public Long atomic(){
                return IRC.login(name);
            }}).execute();
        }

        public static void logout(final long sessionId) {
            new TransactionT.V(){public void atomicV(){
                IRC.logout(sessionId);
            }}.execute();
        }
        
        public static void createRoom(final String roomName) {
            new TransactionT.V(){public void atomicV(){
                IRC.createRoom(roomName);
            }}.execute();
        }

        public static void joinRoom(final long sessionId, final String roomName) {
            (new TransactionT.V(){public void atomicV(){
                IRC.joinRoom(sessionId, roomName);
            }}).execute();
        }

        public static boolean leaveRoom(final long sessionId, final String roomName) {
            return (new TransactionT<Boolean>(){public Boolean atomic(){
                return IRC.leaveRoom(sessionId, roomName);
            }}).execute();
        }
    }
}